#include "stdafx.h"

#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "Assembler.h"
#include "Project64-core/N64System/Mips/R4300iOpcode.h"

ASM_PARSE_ERROR CAssembler::m_ParseError = ERR_NONE;
uint32_t CAssembler::m_Address = 0;

char * CAssembler::m_TokContext = nullptr;

const ASM_SYNTAX_FN CAssembler::syn_jump[] = {arg_jump, nullptr};
const ASM_SYNTAX_FN CAssembler::syn_loadstore[] = {arg_reg_t, arg_imm16, arg_reg_s, nullptr};
const ASM_SYNTAX_FN CAssembler::syn_arith[] = {arg_reg_d, arg_reg_s, arg_reg_t, nullptr};
const ASM_SYNTAX_FN CAssembler::syn_arith2[] = {arg_reg_s, arg_reg_t, nullptr};
const ASM_SYNTAX_FN CAssembler::syn_shiftv[] = {arg_reg_d, arg_reg_t, arg_reg_s, nullptr};
const ASM_SYNTAX_FN CAssembler::syn_arith_i[] = {arg_reg_t, arg_reg_s, arg_imm16, nullptr};
const ASM_SYNTAX_FN CAssembler::syn_load_i[] = {arg_reg_t, arg_imm16, nullptr};
const ASM_SYNTAX_FN CAssembler::syn_branch_z[] = {arg_reg_s, arg_bra_target, nullptr};
const ASM_SYNTAX_FN CAssembler::syn_branch[] = {arg_reg_s, arg_reg_t, arg_bra_target, nullptr};
const ASM_SYNTAX_FN CAssembler::syn_branch_unc[] = {arg_bra_target, nullptr};
const ASM_SYNTAX_FN CAssembler::syn_trap_i[] = {arg_reg_s, arg_imm16, nullptr};
const ASM_SYNTAX_FN CAssembler::syn_shift[] = {arg_reg_d, arg_reg_t, arg_shamt, nullptr};
const ASM_SYNTAX_FN CAssembler::syn_mf[] = {arg_reg_d, nullptr};
const ASM_SYNTAX_FN CAssembler::syn_jr[] = {arg_reg_s, nullptr};
const ASM_SYNTAX_FN CAssembler::syn_jalr[] = {arg_reg_d, arg_reg_s, nullptr};
const ASM_SYNTAX_FN CAssembler::syn_cop1_arith[] = {arg_reg_fd, arg_reg_fs, arg_reg_ft, nullptr};
const ASM_SYNTAX_FN CAssembler::syn_cop1[] = {arg_reg_fd, arg_reg_fs, nullptr};
const ASM_SYNTAX_FN CAssembler::syn_cop1_cmp[] = {arg_reg_fs, arg_reg_ft, nullptr};
const ASM_SYNTAX_FN CAssembler::syn_cop_mv[] = {arg_reg_t, arg_reg_d, nullptr};
const ASM_SYNTAX_FN CAssembler::syn_cache[] = {arg_cache_op, arg_imm16, arg_reg_s, nullptr};
const ASM_SYNTAX_FN CAssembler::syn_syscall[] = {arg_syscall_code, nullptr};

const ASM_INSTRUCTION CAssembler::m_Instructions[] = {
    {"j", R4300i_J, base_op, syn_jump},
    {"jal", R4300i_JAL, base_op, syn_jump},
    {"beq", R4300i_BEQ, base_op, syn_branch},
    {"beqz", R4300i_BEQ, base_op, syn_branch_z},
    {"b", R4300i_BEQ, base_op, syn_branch_unc},
    {"bne", R4300i_BNE, base_op, syn_branch},
    {"bnez", R4300i_BNE, base_op, syn_branch_z},
    {"blez", R4300i_BLEZ, base_op, syn_branch_z},
    {"bgtz", R4300i_BGTZ, base_op, syn_branch_z},
    {"addi", R4300i_ADDI, base_op, syn_arith_i},
    {"addiu", R4300i_ADDIU, base_op, syn_arith_i},
    {"slti", R4300i_SLTI, base_op, syn_arith_i},
    {"sltiu", R4300i_SLTIU, base_op, syn_arith_i},
    {"andi", R4300i_ANDI, base_op, syn_arith_i},
    {"ori", R4300i_ORI, base_op, syn_arith_i},
    {"xori", R4300i_XORI, base_op, syn_arith_i},
    {"lui", R4300i_LUI, base_op, syn_load_i},
    {"beql", R4300i_BEQL, base_op, syn_branch},
    {"beqzl", R4300i_BEQL, base_op, syn_branch_z},
    {"bnel", R4300i_BNEL, base_op, syn_branch},
    {"bnezl", R4300i_BNEL, base_op, syn_branch_z},
    {"blezl", R4300i_BLEZL, base_op, syn_branch_z},
    {"bgtzl", R4300i_BGTZL, base_op, syn_branch_z},
    {"daddi", R4300i_DADDI, base_op, syn_arith_i},
    {"daddiu", R4300i_DADDIU, base_op, syn_arith_i},
    {"ldl", R4300i_LDL, base_op, syn_loadstore},
    {"ldr", R4300i_LDR, base_op, syn_loadstore},
    {"lb", R4300i_LB, base_op, syn_loadstore},
    {"lh", R4300i_LH, base_op, syn_loadstore},
    {"lwl", R4300i_LWL, base_op, syn_loadstore},
    {"lw", R4300i_LW, base_op, syn_loadstore},
    {"lbu", R4300i_LBU, base_op, syn_loadstore},
    {"lhu", R4300i_LHU, base_op, syn_loadstore},
    {"lwr", R4300i_LWR, base_op, syn_loadstore},
    {"lwu", R4300i_LWU, base_op, syn_loadstore},
    {"sb", R4300i_SB, base_op, syn_loadstore},
    {"sh", R4300i_SH, base_op, syn_loadstore},
    {"swl", R4300i_SWL, base_op, syn_loadstore},
    {"sw", R4300i_SW, base_op, syn_loadstore},
    {"sdl", R4300i_SDL, base_op, syn_loadstore},
    {"sdr", R4300i_SDR, base_op, syn_loadstore},
    {"swr", R4300i_SWR, base_op, syn_loadstore},
    {"cache", R4300i_CACHE, base_op, syn_cache},
    {"ll", R4300i_LL, base_op, syn_loadstore},
    {"lwc1", R4300i_LWC1, base_op, syn_loadstore},
    {"ldc1", R4300i_LDC1, base_op, syn_loadstore},
    {"ld", R4300i_LD, base_op, syn_loadstore},
    {"sc", R4300i_SC, base_op, syn_loadstore},
    {"swc1", R4300i_SWC1, base_op, syn_loadstore},
    {"sdc1", R4300i_SDC1, base_op, syn_loadstore},
    {"sdc2", R4300i_SDC2, base_op, syn_loadstore},
    {"sd", R4300i_SD, base_op, syn_loadstore},

    {"sll", R4300i_SPECIAL_SLL, base_spec, syn_shift},
    {"nop", R4300i_SPECIAL_SLL, base_spec, nullptr},
    {"srl", R4300i_SPECIAL_SRL, base_spec, syn_shift},
    {"sra", R4300i_SPECIAL_SRA, base_spec, syn_shift},
    {"sllv", R4300i_SPECIAL_SLLV, base_spec, syn_shiftv},
    {"srlv", R4300i_SPECIAL_SRLV, base_spec, syn_shiftv},
    {"srav", R4300i_SPECIAL_SRAV, base_spec, syn_shiftv},
    {"jr", R4300i_SPECIAL_JR, base_spec, syn_jr},
    {"jalr", R4300i_SPECIAL_JALR, base_spec, syn_jalr},
    {"jalr", R4300i_SPECIAL_JALR, base_spec_jalr_ra, syn_jr},
    {"syscall", R4300i_SPECIAL_SYSCALL, base_spec, syn_syscall},
    {"break", R4300i_SPECIAL_BREAK, base_spec, syn_syscall},
    {"sync", R4300i_SPECIAL_SYNC, base_spec, nullptr},
    {"mfhi", R4300i_SPECIAL_MFHI, base_spec, syn_mf},
    {"mthi", R4300i_SPECIAL_MTHI, base_spec, syn_mf},
    {"mflo", R4300i_SPECIAL_MFLO, base_spec, syn_mf},
    {"mtlo", R4300i_SPECIAL_MTLO, base_spec, syn_mf},
    {"dsllv", R4300i_SPECIAL_DSLLV, base_spec, syn_shiftv},
    {"dsrlv", R4300i_SPECIAL_DSRLV, base_spec, syn_shiftv},
    {"dsrav", R4300i_SPECIAL_DSRAV, base_spec, syn_shiftv},
    {"mult", R4300i_SPECIAL_MULT, base_spec, syn_arith2},
    {"multu", R4300i_SPECIAL_MULTU, base_spec, syn_arith2},
    {"div", R4300i_SPECIAL_DIV, base_spec, syn_arith2},
    {"divu", R4300i_SPECIAL_DIVU, base_spec, syn_arith2},
    {"dmult", R4300i_SPECIAL_DMULT, base_spec, syn_arith2},
    {"dmultu", R4300i_SPECIAL_DMULTU, base_spec, syn_arith2},
    {"ddiv", R4300i_SPECIAL_DDIV, base_spec, syn_arith2},
    {"ddivu", R4300i_SPECIAL_DDIVU, base_spec, syn_arith2},
    {"add", R4300i_SPECIAL_ADD, base_spec, syn_arith},
    {"addu", R4300i_SPECIAL_ADDU, base_spec, syn_arith},
    {"sub", R4300i_SPECIAL_SUB, base_spec, syn_arith},
    {"subu", R4300i_SPECIAL_SUBU, base_spec, syn_arith},
    {"and", R4300i_SPECIAL_AND, base_spec, syn_arith},
    {"or", R4300i_SPECIAL_OR, base_spec, syn_arith},
    {"xor", R4300i_SPECIAL_XOR, base_spec, syn_arith},
    {"nor", R4300i_SPECIAL_NOR, base_spec, syn_arith},
    {"slt", R4300i_SPECIAL_SLT, base_spec, syn_arith},
    {"sltu", R4300i_SPECIAL_SLTU, base_spec, syn_arith},
    {"dadd", R4300i_SPECIAL_DADD, base_spec, syn_arith},
    {"daddu", R4300i_SPECIAL_DADDU, base_spec, syn_arith},
    {"dsub", R4300i_SPECIAL_DSUB, base_spec, syn_arith},
    {"dsubu", R4300i_SPECIAL_DSUBU, base_spec, syn_arith},
    {"tge", R4300i_SPECIAL_TGE, base_spec, syn_arith2},   // Note: no code field
    {"tgeu", R4300i_SPECIAL_TGEU, base_spec, syn_arith2}, //
    {"tlt", R4300i_SPECIAL_TLT, base_spec, syn_arith2},   //
    {"tltu", R4300i_SPECIAL_TLTU, base_spec, syn_arith2}, //
    {"teq", R4300i_SPECIAL_TEQ, base_spec, syn_arith2},   //
    {"tne", R4300i_SPECIAL_TNE, base_spec, syn_arith2},   //
    {"dsll", R4300i_SPECIAL_DSLL, base_spec, syn_shift},
    {"dsrl", R4300i_SPECIAL_DSRL, base_spec, syn_shift},
    {"dsra", R4300i_SPECIAL_DSRA, base_spec, syn_shift},
    {"dsll32", R4300i_SPECIAL_DSLL32, base_spec, syn_shift},
    {"dsrl32", R4300i_SPECIAL_DSRL32, base_spec, syn_shift},
    {"dsra32", R4300i_SPECIAL_DSRA32, base_spec, syn_shift},

    {"bltz", R4300i_REGIMM_BLTZ, base_regimm, syn_branch_z},
    {"bgez", R4300i_REGIMM_BGEZ, base_regimm, syn_branch_z},
    {"bltzl", R4300i_REGIMM_BLTZL, base_regimm, syn_branch_z},
    {"bgezl", R4300i_REGIMM_BGEZL, base_regimm, syn_branch_z},
    {"tgei", R4300i_REGIMM_TGEI, base_regimm, syn_trap_i},
    {"tgeiu", R4300i_REGIMM_TGEIU, base_regimm, syn_trap_i},
    {"tlti", R4300i_REGIMM_TLTI, base_regimm, syn_trap_i},
    {"tltiu", R4300i_REGIMM_TLTIU, base_regimm, syn_trap_i},
    {"teqi", R4300i_REGIMM_TEQI, base_regimm, syn_trap_i},
    {"tnei", R4300i_REGIMM_TNEI, base_regimm, syn_trap_i},
    {"bltzal", R4300i_REGIMM_BLTZAL, base_regimm, syn_branch_z},
    {"bgezal", R4300i_REGIMM_BGEZAL, base_regimm, syn_branch_z},
    {"bal", R4300i_REGIMM_BGEZAL, base_regimm, syn_branch_unc},
    {"bltzall", R4300i_REGIMM_BLTZALL, base_regimm, syn_branch_z},
    {"bgezall", R4300i_REGIMM_BGEZALL, base_regimm, syn_branch_z},

    {"mfc0", R4300i_COP0_MF, base_cop0_mv, syn_cop_mv},
    {"mtc0", R4300i_COP0_MT, base_cop0_mv, syn_cop_mv},

    {"tlbr", R4300i_COP0_CO_TLBR, base_cop0_co, nullptr},
    {"tlbwi", R4300i_COP0_CO_TLBWI, base_cop0_co, nullptr},
    {"tlbwr", R4300i_COP0_CO_TLBWR, base_cop0_co, nullptr},
    {"tlbp", R4300i_COP0_CO_TLBP, base_cop0_co, nullptr},
    {"eret", R4300i_COP0_CO_ERET, base_cop0_co, nullptr},

    {"mfc1", R4300i_COP1_MF, base_cop1_mv, syn_cop_mv},
    {"dmfc1", R4300i_COP1_DMF, base_cop1_mv, syn_cop_mv},
    {"cfc1", R4300i_COP1_CF, base_cop1_mv, syn_cop_mv},
    {"mtc1", R4300i_COP1_MT, base_cop1_mv, syn_cop_mv},
    {"dmtc1", R4300i_COP1_DMT, base_cop1_mv, syn_cop_mv},
    {"ctc1", R4300i_COP1_CT, base_cop1_mv, syn_cop_mv},

    {"bc1f", R4300i_COP1_BC_BCF, base_cop1_bc, syn_branch_unc},
    {"bc1t", R4300i_COP1_BC_BCT, base_cop1_bc, syn_branch_unc},
    {"bc1fl", R4300i_COP1_BC_BCFL, base_cop1_bc, syn_branch_unc},
    {"bc1tl", R4300i_COP1_BC_BCTL, base_cop1_bc, syn_branch_unc},

    {"add.s", R4300i_COP1_FUNCT_ADD, base_cop1_s, syn_cop1_arith},
    {"sub.s", R4300i_COP1_FUNCT_SUB, base_cop1_s, syn_cop1_arith},
    {"mul.s", R4300i_COP1_FUNCT_MUL, base_cop1_s, syn_cop1_arith},
    {"div.s", R4300i_COP1_FUNCT_DIV, base_cop1_s, syn_cop1_arith},
    {"sqrt.s", R4300i_COP1_FUNCT_SQRT, base_cop1_s, syn_cop1},
    {"abs.s", R4300i_COP1_FUNCT_ABS, base_cop1_s, syn_cop1},
    {"mov.s", R4300i_COP1_FUNCT_MOV, base_cop1_s, syn_cop1},
    {"neg.s", R4300i_COP1_FUNCT_NEG, base_cop1_s, syn_cop1},
    {"round.l.s", R4300i_COP1_FUNCT_ROUND_L, base_cop1_s, syn_cop1},
    {"trunc.l.s", R4300i_COP1_FUNCT_TRUNC_L, base_cop1_s, syn_cop1},
    {"ceil.l.s", R4300i_COP1_FUNCT_CEIL_L, base_cop1_s, syn_cop1},
    {"floor.l.s", R4300i_COP1_FUNCT_FLOOR_L, base_cop1_s, syn_cop1},
    {"round.w.s", R4300i_COP1_FUNCT_ROUND_W, base_cop1_s, syn_cop1},
    {"trunc.w.s", R4300i_COP1_FUNCT_TRUNC_W, base_cop1_s, syn_cop1},
    {"ceil.w.s", R4300i_COP1_FUNCT_CEIL_W, base_cop1_s, syn_cop1},
    {"floor.w.s", R4300i_COP1_FUNCT_FLOOR_W, base_cop1_s, syn_cop1},
    {"cvt.s.s", R4300i_COP1_FUNCT_CVT_S, base_cop1_s, syn_cop1},
    {"cvt.d.s", R4300i_COP1_FUNCT_CVT_D, base_cop1_s, syn_cop1},
    {"cvt.w.s", R4300i_COP1_FUNCT_CVT_W, base_cop1_s, syn_cop1},
    {"cvt.l.s", R4300i_COP1_FUNCT_CVT_L, base_cop1_s, syn_cop1},
    {"c.f.s", R4300i_COP1_FUNCT_C_F, base_cop1_s, syn_cop1_cmp},
    {"c.un.s", R4300i_COP1_FUNCT_C_UN, base_cop1_s, syn_cop1_cmp},
    {"c.eq.s", R4300i_COP1_FUNCT_C_EQ, base_cop1_s, syn_cop1_cmp},
    {"c.ueq.s", R4300i_COP1_FUNCT_C_UEQ, base_cop1_s, syn_cop1_cmp},
    {"c.olt.s", R4300i_COP1_FUNCT_C_OLT, base_cop1_s, syn_cop1_cmp},
    {"c.ult.s", R4300i_COP1_FUNCT_C_ULT, base_cop1_s, syn_cop1_cmp},
    {"c.ole.s", R4300i_COP1_FUNCT_C_OLE, base_cop1_s, syn_cop1_cmp},
    {"c.ule.s", R4300i_COP1_FUNCT_C_ULE, base_cop1_s, syn_cop1_cmp},
    {"c.sf.s", R4300i_COP1_FUNCT_C_SF, base_cop1_s, syn_cop1_cmp},
    {"c.ngle.s", R4300i_COP1_FUNCT_C_NGLE, base_cop1_s, syn_cop1_cmp},
    {"c.seq.s", R4300i_COP1_FUNCT_C_SEQ, base_cop1_s, syn_cop1_cmp},
    {"c.ngl.s", R4300i_COP1_FUNCT_C_NGL, base_cop1_s, syn_cop1_cmp},
    {"c.lt.s", R4300i_COP1_FUNCT_C_LT, base_cop1_s, syn_cop1_cmp},
    {"c.nge.s", R4300i_COP1_FUNCT_C_NGE, base_cop1_s, syn_cop1_cmp},
    {"c.le.s", R4300i_COP1_FUNCT_C_LE, base_cop1_s, syn_cop1_cmp},
    {"c.ngt.s", R4300i_COP1_FUNCT_C_NGT, base_cop1_s, syn_cop1_cmp},

    {"add.d", R4300i_COP1_FUNCT_ADD, base_cop1_d, syn_cop1_arith},
    {"sub.d", R4300i_COP1_FUNCT_SUB, base_cop1_d, syn_cop1_arith},
    {"mul.d", R4300i_COP1_FUNCT_MUL, base_cop1_d, syn_cop1_arith},
    {"div.d", R4300i_COP1_FUNCT_DIV, base_cop1_d, syn_cop1_arith},
    {"sqrt.d", R4300i_COP1_FUNCT_SQRT, base_cop1_d, syn_cop1},
    {"abs.d", R4300i_COP1_FUNCT_ABS, base_cop1_d, syn_cop1},
    {"mov.d", R4300i_COP1_FUNCT_MOV, base_cop1_d, syn_cop1},
    {"neg.d", R4300i_COP1_FUNCT_NEG, base_cop1_d, syn_cop1},
    {"round.l.d", R4300i_COP1_FUNCT_ROUND_L, base_cop1_d, syn_cop1},
    {"trunc.l.d", R4300i_COP1_FUNCT_TRUNC_L, base_cop1_d, syn_cop1},
    {"ceil.l.d", R4300i_COP1_FUNCT_CEIL_L, base_cop1_d, syn_cop1},
    {"floor.l.d", R4300i_COP1_FUNCT_FLOOR_L, base_cop1_d, syn_cop1},
    {"round.w.d", R4300i_COP1_FUNCT_ROUND_W, base_cop1_d, syn_cop1},
    {"trunc.w.d", R4300i_COP1_FUNCT_TRUNC_W, base_cop1_d, syn_cop1},
    {"ceil.w.d", R4300i_COP1_FUNCT_CEIL_W, base_cop1_d, syn_cop1},
    {"floor.w.d", R4300i_COP1_FUNCT_FLOOR_W, base_cop1_d, syn_cop1},
    {"cvt.s.d", R4300i_COP1_FUNCT_CVT_S, base_cop1_d, syn_cop1},
    {"cvt.d.d", R4300i_COP1_FUNCT_CVT_D, base_cop1_d, syn_cop1},
    {"cvt.w.d", R4300i_COP1_FUNCT_CVT_W, base_cop1_d, syn_cop1},
    {"cvt.l.d", R4300i_COP1_FUNCT_CVT_L, base_cop1_d, syn_cop1},
    {"c.f.d", R4300i_COP1_FUNCT_C_F, base_cop1_d, syn_cop1_cmp},
    {"c.un.d", R4300i_COP1_FUNCT_C_UN, base_cop1_d, syn_cop1_cmp},
    {"c.eq.d", R4300i_COP1_FUNCT_C_EQ, base_cop1_d, syn_cop1_cmp},
    {"c.ueq.d", R4300i_COP1_FUNCT_C_UEQ, base_cop1_d, syn_cop1_cmp},
    {"c.olt.d", R4300i_COP1_FUNCT_C_OLT, base_cop1_d, syn_cop1_cmp},
    {"c.ult.d", R4300i_COP1_FUNCT_C_ULT, base_cop1_d, syn_cop1_cmp},
    {"c.ole.d", R4300i_COP1_FUNCT_C_OLE, base_cop1_d, syn_cop1_cmp},
    {"c.ule.d", R4300i_COP1_FUNCT_C_ULE, base_cop1_d, syn_cop1_cmp},
    {"c.sf.d", R4300i_COP1_FUNCT_C_SF, base_cop1_d, syn_cop1_cmp},
    {"c.ngle.d", R4300i_COP1_FUNCT_C_NGLE, base_cop1_d, syn_cop1_cmp},
    {"c.seq.d", R4300i_COP1_FUNCT_C_SEQ, base_cop1_d, syn_cop1_cmp},
    {"c.ngl.d", R4300i_COP1_FUNCT_C_NGL, base_cop1_d, syn_cop1_cmp},
    {"c.lt.d", R4300i_COP1_FUNCT_C_LT, base_cop1_d, syn_cop1_cmp},
    {"c.nge.d", R4300i_COP1_FUNCT_C_NGE, base_cop1_d, syn_cop1_cmp},
    {"c.le.d", R4300i_COP1_FUNCT_C_LE, base_cop1_d, syn_cop1_cmp},
    {"c.ngt.d", R4300i_COP1_FUNCT_C_NGT, base_cop1_d, syn_cop1_cmp},

    {"cvt.s.w", R4300i_COP1_FUNCT_CVT_S, base_cop1_w, syn_cop1},
    {"cvt.d.w", R4300i_COP1_FUNCT_CVT_D, base_cop1_w, syn_cop1},
    {"cvt.s.l", R4300i_COP1_FUNCT_CVT_S, base_cop1_l, syn_cop1},
    {"cvt.d.l", R4300i_COP1_FUNCT_CVT_D, base_cop1_l, syn_cop1},
    {nullptr},
};

const ASM_REGISTER CAssembler::m_Registers[] = {
    {"r0", 0},
    {"at", 1},
    {"v0", 2},
    {"v1", 3},
    {"a0", 4},
    {"a1", 5},
    {"a2", 6},
    {"a3", 7},
    {"t0", 8},
    {"t1", 9},
    {"t2", 10},
    {"t3", 11},
    {"t4", 12},
    {"t5", 13},
    {"t6", 14},
    {"t7", 15},
    {"s0", 16},
    {"s1", 17},
    {"s2", 18},
    {"s3", 19},
    {"s4", 20},
    {"s5", 21},
    {"s6", 22},
    {"s7", 23},
    {"t8", 24},
    {"t9", 25},
    {"k0", 26},
    {"k1", 27},
    {"gp", 28},
    {"sp", 29},
    {"s8", 30},
    {"ra", 31},

    {"fp", 30},

    {"r0", 0},
    {"r1", 1},
    {"r2", 2},
    {"r3", 3},
    {"r4", 4},
    {"r5", 5},
    {"r6", 6},
    {"r7", 7},
    {"r8", 8},
    {"r9", 9},
    {"r10", 10},
    {"r11", 11},
    {"r12", 12},
    {"r13", 13},
    {"r14", 14},
    {"r15", 15},
    {"r16", 16},
    {"r17", 17},
    {"r18", 18},
    {"r19", 19},
    {"r20", 20},
    {"r21", 21},
    {"r22", 22},
    {"r23", 23},
    {"r24", 24},
    {"r25", 25},
    {"r26", 26},
    {"r27", 27},
    {"r28", 28},
    {"r29", 29},
    {"r30", 30},
    {"r31", 31},

    {"f0", 0},
    {"f1", 1},
    {"f2", 2},
    {"f3", 3},
    {"f4", 4},
    {"f5", 5},
    {"f6", 6},
    {"f7", 7},
    {"f8", 8},
    {"f9", 9},
    {"f10", 10},
    {"f11", 11},
    {"f12", 12},
    {"f13", 13},
    {"f14", 14},
    {"f15", 15},
    {"f16", 16},
    {"f17", 17},
    {"f18", 18},
    {"f19", 19},
    {"f20", 20},
    {"f21", 21},
    {"f22", 22},
    {"f23", 23},
    {"f24", 24},
    {"f25", 25},
    {"f26", 26},
    {"f27", 27},
    {"f28", 28},
    {"f29", 29},
    {"f30", 30},
    {"f31", 31},

    {"revision", 0},
    {"fcsr", 31},
    {"fcs", 31},

    {"index", 0},
    {"random", 1},
    {"entrylo0", 2},
    {"entrylo1", 3},
    {"context", 4},
    {"pagemask", 5},
    {"wired", 6},
    {"badvaddr", 8},
    {"count", 9},
    {"entryhi", 10},
    {"compare", 11},
    {"status", 12},
    {"cause", 13},
    {"epc", 14},
    {"prid", 15},
    {"config", 16},
    {"lladdr", 17},
    {"watchlo", 18},
    {"watchhi", 19},
    {"xcontext", 20},
    {"ecc", 26},
    {"cacheerr", 27},
    {"taglo", 28},
    {"taghi", 29},
    {"errepc", 30},

    {nullptr},
};

bool CAssembler::AssembleLine(const char * line, uint32_t * opcode, uint32_t address)
{
    m_ParseError = ERR_NONE;
    m_Address = address;

    char line_c[128];
    strncpy(line_c, line, 128);
    StrToLower(line_c);

    if (line_c[0] == '\0')
    {
        *opcode = 0;
        return true;
    }

    char * name = strtok_s(line_c, " \t", &m_TokContext);

    // Attempt to assemble the line
    // If a syntax error occurs, check if the command has alternative syntax forms and retry

    for (int nFallback = 0;; nFallback++)
    {
        const ASM_INSTRUCTION * instruction = LookupInstruction(name, nFallback);

        if (instruction == nullptr)
        {
            m_ParseError = ERR_UNKNOWN_CMD;
            return false;
        }

        m_ParseError = ERR_NONE;

        if (nFallback > 0)
        {
            // Prepare for re-tokenization
            strncpy(line_c, line, 128);
            StrToLower(line_c);
            name = strtok_s(line_c, " \t", &m_TokContext);
        }

        *opcode = instruction->base(instruction->val);

        if (instruction->syntax == nullptr)
        {
            // No parameters
            return true;
        }

        for (int i = 0; instruction->syntax[i]; i++)
        {
            instruction->syntax[i](opcode);

            if (m_ParseError != ERR_NONE)
            {
                goto next_fallback;
            }
        }

        // Assembled without errors
        return true;

    next_fallback:;
    }
}

const ASM_INSTRUCTION * CAssembler::LookupInstruction(char * name, int nFallback)
{
    for (int i = 0; m_Instructions[i].name != nullptr; i++)
    {
        if (strcmp(name, m_Instructions[i].name) == 0)
        {
            if (nFallback != 0)
            {
                nFallback--;
                continue;
            }
            return &m_Instructions[i];
        }
    }
    return nullptr;
}

const ASM_REGISTER * CAssembler::LookupRegister(char * name)
{
    for (int i = 0; m_Registers[i].name != nullptr; i++)
    {
        if (strcmp(name, m_Registers[i].name) == 0)
        {
            return &m_Registers[i];
        }
    }
    return nullptr;
}

void CAssembler::StrToLower(char * str)
{
    while (*str)
    {
        if (*str >= 'A' && *str <= 'Z')
        {
            *str |= 0x20;
        }
        str++;
    }
}

uint32_t CAssembler::pop_reg()
{
    char * r = strtok_s(nullptr, " \t,()", &m_TokContext);

    if (r == nullptr)
    {
        m_ParseError = ERR_EXPECTED_REG;
        return 0;
    }

    const ASM_REGISTER * reg = LookupRegister(r);

    if (reg == nullptr)
    {
        m_ParseError = ERR_INVALID_REG;
        return 0;
    }

    return reg->val;
}

uint32_t CAssembler::pop_val()
{
    char * v = strtok_s(nullptr, " \t,()", &m_TokContext);

    if (v == nullptr)
    {
        m_ParseError = ERR_EXPECTED_VAL;
        return 0;
    }

    //if (isalpha(*v))
    //{
    //    // TODO: lookup label value
    //    return 0;
    //}

    int base = 0; // Hexadecimal or decimal

    if (*v == '$')
    {
        base = 16; // Hexadecimal
        v++;
    }

    char * endptr;

    uint32_t val = strtoul(v, &endptr, base);

    if (*endptr != '\0')
    {
        m_ParseError = ERR_EXPECTED_VAL;
        return 0;
    }

    return val;
}

uint32_t CAssembler::base_op(uint32_t val)
{
    return val << 26;
}

uint32_t CAssembler::base_spec(uint32_t val)
{
    return val;
}

uint32_t CAssembler::base_spec_jalr_ra(uint32_t val)
{
    return (31 << 11) | val;
}

uint32_t CAssembler::base_regimm(uint32_t val)
{
    return (R4300i_REGIMM << 26) | (val << 16);
}

uint32_t CAssembler::base_cop0_co(uint32_t val)
{
    return (R4300i_CP0 << 26) | (1 << 25) | val;
}

uint32_t CAssembler::base_cop0_mv(uint32_t val)
{
    return (R4300i_CP0 << 26) | (val << 21);
}

uint32_t CAssembler::base_cop1_mv(uint32_t val)
{
    return (R4300i_CP1 << 26) | (val << 21);
}

uint32_t CAssembler::base_cop1_s(uint32_t val)
{
    return (R4300i_CP1 << 26) | (R4300i_COP1_S << 21) | val;
}

uint32_t CAssembler::base_cop1_d(uint32_t val)
{
    return (R4300i_CP1 << 26) | (R4300i_COP1_D << 21) | val;
}

uint32_t CAssembler::base_cop1_w(uint32_t val)
{
    return (R4300i_CP1 << 26) | (R4300i_COP1_W << 21) | val;
}

uint32_t CAssembler::base_cop1_l(uint32_t val)
{
    return (R4300i_CP1 << 26) | (R4300i_COP1_L << 21) | val;
}

uint32_t CAssembler::base_cop1_bc(uint32_t val)
{
    return (R4300i_CP1 << 26) | (R4300i_COP1_BC << 21) | (val << 16);
}

void CAssembler::arg_reg_t(uint32_t * opcode)
{
    *opcode |= pop_reg() << 16;
}

void CAssembler::arg_reg_s(uint32_t * opcode)
{
    *opcode |= pop_reg() << 21;
}

void CAssembler::arg_reg_d(uint32_t * opcode)
{
    *opcode |= pop_reg() << 11;
}

void CAssembler::arg_reg_ft(uint32_t * opcode)
{
    *opcode |= pop_reg() << 16;
}

void CAssembler::arg_reg_fs(uint32_t * opcode)
{
    *opcode |= pop_reg() << 11;
}

void CAssembler::arg_reg_fd(uint32_t * opcode)
{
    *opcode |= pop_reg() << 6;
}

void CAssembler::arg_jump(uint32_t * opcode)
{
    *opcode |= (pop_val() / 4) & 0x3FFFFFF;
}

void CAssembler::arg_imm16(uint32_t * opcode)
{
    *opcode |= (pop_val() & 0xFFFF);
}

void CAssembler::arg_bra_target(uint32_t * opcode)
{
    uint16_t relTarget = (((pop_val() - m_Address) / 4) & 0xFFFF) - 1;
    *opcode |= relTarget;
}

void CAssembler::arg_shamt(uint32_t * opcode)
{
    *opcode |= (pop_val() & 0x1F) << 6;
}

void CAssembler::arg_cache_op(uint32_t * opcode)
{
    *opcode |= (pop_val() & 0x1F) << 16;
}

void CAssembler::arg_syscall_code(uint32_t * opcode)
{
    *opcode |= (pop_val() & 0xFFFFF) << 6;
}
